// COPIED FROM org.eclipse.jface.internal.text.html
// to get around "discouraged access" errors
/*******************************************************************************
 * Copyright (c) 2000, 2007 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.wst.jsdt.internal.ui.text.html;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.Iterator;

import org.eclipse.jface.text.DefaultInformationControl;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextPresentation;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.graphics.Drawable;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.widgets.Display;

import com.ibm.icu.text.BreakIterator;


/**
 * <p>
 * Moved into this package from <code>org.eclipse.jface.internal.text.revisions</code>.</p>
 */
public class HTMLTextPresenter implements DefaultInformationControl.IInformationPresenter, DefaultInformationControl.IInformationPresenterExtension {

	public class LineBreakingReader {

		private BufferedReader fReader;
		private GC fGC;
		private int fMaxWidth;

		private String fLine;
		private int fOffset;

		private BreakIterator fLineBreakIterator;
		private boolean fBreakWords;

		/**
		 * Creates a reader that breaks an input text to fit in a given width.
		 * 
		 * @param reader Reader of the input text
		 * @param gc The graphic context that defines the currently used font sizes
		 * @param maxLineWidth The max width (pixels) where the text has to fit in
		 */
		public LineBreakingReader(Reader reader, GC gc, int maxLineWidth) {
			fReader= new BufferedReader(reader);
			fGC= gc;
			fMaxWidth= maxLineWidth;
			fOffset= 0;
			fLine= null;
			fLineBreakIterator= BreakIterator.getLineInstance();
			fBreakWords= true;
		}

		public boolean isFormattedLine() {
			return fLine != null;
		}

		/**
		 * Reads the next line. The lengths of the line will not exceed the given maximum
		 * width.
		 * 
		 * @return the next line 
		 * @throws IOException 
		 */
		public String readLine() throws IOException {
			if (fLine == null) {
				String line= fReader.readLine();
				if (line == null) {
					return null;
				}

				int lineLen= fGC.textExtent(line).x;
				if (lineLen < fMaxWidth) {
					return line;
				}
				fLine= line;
				fLineBreakIterator.setText(line);
				fOffset= 0;
			}
			int breakOffset= findNextBreakOffset(fOffset);
			String res;
			if (breakOffset != BreakIterator.DONE) {
				res= fLine.substring(fOffset, breakOffset);
				fOffset= findWordBegin(breakOffset);
				if (fOffset == fLine.length()) {
					fLine= null;
				}
			} else {
				res= fLine.substring(fOffset);
				fLine= null;
			}
			return res;
		}

		private int findNextBreakOffset(int currOffset) {
			int currWidth= 0;
			int nextOffset= fLineBreakIterator.following(currOffset);
			while (nextOffset != BreakIterator.DONE) {
				String word= fLine.substring(currOffset, nextOffset);
				int wordWidth= fGC.textExtent(word).x;
				int nextWidth= wordWidth + currWidth;
				if (nextWidth > fMaxWidth) {
					if (currWidth > 0) {
						return currOffset;
					}

					if (!fBreakWords) {
						return nextOffset;
					}

					// need to fit into fMaxWidth
					int length= word.length();
					while (length >= 0) {
						length--;
						word= word.substring(0, length);
						wordWidth= fGC.textExtent(word).x;
						if ((wordWidth + currWidth) < fMaxWidth) {
							return currOffset + length;
						}
					}
					return nextOffset;
				}
				currWidth= nextWidth;
				currOffset= nextOffset;
				nextOffset= fLineBreakIterator.next();
			}
			return nextOffset;
		}

		private int findWordBegin(int idx) {
			while ((idx < fLine.length()) && Character.isWhitespace(fLine.charAt(idx))) {
				idx++;
			}
			return idx;
		}
	}
	private static final String LINE_DELIM= System.getProperty("line.separator", "\n"); //$NON-NLS-1$ //$NON-NLS-2$

	private int fCounter;
	private boolean fEnforceUpperLineLimit;

	public HTMLTextPresenter(boolean enforceUpperLineLimit) {
		super();
		fEnforceUpperLineLimit= enforceUpperLineLimit;
	}

	public HTMLTextPresenter() {
		this(true);
	}

	protected Reader createReader(String hoverInfo, TextPresentation presentation) {
		return new HTML2TextReader(new StringReader(hoverInfo), presentation);
	}

	protected void adaptTextPresentation(TextPresentation presentation, int offset, int insertLength) {

		int yoursStart= offset;
		int yoursEnd=   (offset + insertLength) -1;
		yoursEnd= Math.max(yoursStart, yoursEnd);

		Iterator e= presentation.getAllStyleRangeIterator();
		while (e.hasNext()) {

			StyleRange range= (StyleRange) e.next();

			int myStart= range.start;
			int myEnd=   (range.start + range.length) -1;
			myEnd= Math.max(myStart, myEnd);

			if (myEnd < yoursStart) {
				continue;
			}

			if (myStart < yoursStart) {
				range.length += insertLength;
			} else {
				range.start += insertLength;
			}
		}
	}

	private void append(StringBuffer buffer, String string, TextPresentation presentation) {

		int length= string.length();
		buffer.append(string);

		if (presentation != null) {
			adaptTextPresentation(presentation, fCounter, length);
		}

		fCounter += length;
	}

	private String getIndent(String line) {
		int length= line.length();

		int i= 0;
		while ((i < length) && Character.isWhitespace(line.charAt(i))) {
			++i;
		}

		return (i == length ? line : line.substring(0, i)) + " "; //$NON-NLS-1$
	}

	/*
	 * @see IHoverInformationPresenter#updatePresentation(Display display, String, TextPresentation, int, int)
	 */
	public String updatePresentation(Display display, String hoverInfo, TextPresentation presentation, int maxWidth, int maxHeight) {
		return updatePresentation((Drawable)display, hoverInfo, presentation, maxWidth, maxHeight);
	}

	/*
	 * @see IHoverInformationPresenterExtension#updatePresentation(Drawable drawable, String, TextPresentation, int, int)
	 * 
	 */
	public String updatePresentation(Drawable drawable, String hoverInfo, TextPresentation presentation, int maxWidth, int maxHeight) {

		if (hoverInfo == null) {
			return null;
		}

		GC gc= new GC(drawable);
		try {

			StringBuffer buffer= new StringBuffer();
			int maxNumberOfLines= Math.round(maxHeight / gc.getFontMetrics().getHeight());

			fCounter= 0;
			LineBreakingReader reader= new LineBreakingReader(createReader(hoverInfo, presentation), gc, maxWidth);

			boolean lastLineFormatted= false;
			String lastLineIndent= null;

			String line=reader.readLine();
			boolean lineFormatted= reader.isFormattedLine();
			boolean firstLineProcessed= false;

			while (line != null) {

				if (fEnforceUpperLineLimit && (maxNumberOfLines <= 0)) {
					break;
				}

				if (firstLineProcessed) {
					if (!lastLineFormatted) {
						append(buffer, LINE_DELIM, null);
					} else {
						append(buffer, LINE_DELIM, presentation);
						if (lastLineIndent != null) {
							append(buffer, lastLineIndent, presentation);
						}
					}
				}

				append(buffer, line, null);
				firstLineProcessed= true;

				lastLineFormatted= lineFormatted;
				if (!lineFormatted) {
					lastLineIndent= null;
				} else if (lastLineIndent == null) {
					lastLineIndent= getIndent(line);
				}

				line= reader.readLine();
				lineFormatted= reader.isFormattedLine();

				maxNumberOfLines--;
			}

			if (line != null) {
				append(buffer, LINE_DELIM, lineFormatted ? presentation : null);
				append(buffer, HTMLMessages.getString("HTMLTextPresenter.ellipse"), presentation); //$NON-NLS-1$
			}

			return trim(buffer, presentation);

		} catch (IOException e) {

			// ignore TODO do something else?
			return null;

		} finally {
			gc.dispose();
		}
	}

	private String trim(StringBuffer buffer, TextPresentation presentation) {

		int length= buffer.length();

		int end= length -1;
		while ((end >= 0) && Character.isWhitespace(buffer.charAt(end))) {
			-- end;
		}

		if (end == -1)
		 {
			return ""; //$NON-NLS-1$
		}

		if (end < (length -1)) {
			buffer.delete(end + 1, length);
		} else {
			end= length;
		}

		int start= 0;
		while ((start < end) && Character.isWhitespace(buffer.charAt(start))) {
			++ start;
		}

		buffer.delete(0, start);
		presentation.setResultWindow(new Region(start, buffer.length()));
		return buffer.toString();
	}
}

